package edu.siu.math.egut.main;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Scanner;
import java.util.regex.Pattern;

import javax.swing.JFileChooser;

import edu.siu.math.egut.io.Algorithms;
import edu.siu.math.egut.local.DomainReadException;
import edu.siu.math.egut.local.Inequality;
import edu.siu.math.egut.local.InequalityReadException;
import edu.siu.math.egut.local.SectionIntegral;
import edu.siu.math.egut.local.SectionIntegralReadException;
import edu.siu.math.egut.local.UncomputableJacobianException;
import edu.siu.math.egut.local.Inequality.Symbol;
import edu.siu.math.egut.util.BadSubstitutionException;
import edu.siu.math.egut.util.Group;
import edu.siu.math.egut.util.NotARootException;
import edu.siu.math.egut.util.ParabolicReadException;
import edu.siu.math.egut.util.Polynomial;
import edu.siu.math.egut.util.PolynomialReadException;
import edu.siu.math.egut.util.Sunipotent;
import edu.siu.math.egut.util.Unipotent;
import edu.siu.math.egut.util.UnipotentReadException;
import edu.siu.math.egut.util.WeylWord;
import edu.siu.math.egut.util.WeylWordReadException;

/**
 * @author Joseph Hundley
 *
 */
public class SectionIntegralCalculator {
    /**
     * @author Joseph Hundley
     *
     */

    public enum IwasawaBranchResponse {
	TRUNK, INTEGERS, NONINTEGERS

    }


    private static final String ECHO_LOG_FILE = "EGUT_SIC_ECHO_LOG";

 
    /**
     * 
     */
    private String name;
    private SectionIntegral integral;
    private File directory;
    private RandomAccessFile laTeXLog;
    private BufferedWriter plainTextLog;


    private ArrayList<String> stateLog;
    private static BufferedWriter echoLog;

    

    public enum Input {
	PREVIOUS,
	NEXT,
	QUIT, 
	IWASAWA, 
	REORDER, 
	CONJUGATE, 
	SUBSTITUTION, 
	CUSTOMIZE_VARIABLES, 
	WEYL_ELEMENT, 
	ADD, 
	PSI_FACTOR, 
	SAVE, 
	SAVE_AS, 
	LOAD, 
	DROP_LAST, 
	CONSTRAIN,
	LIST_ROOTS,
	LIST_PSI_ROOTS

    }

    private static final String INITIALIZATION_PROMPT = "Please input either " +
    		"the name of a serialized section integral object or the name " +
    		"of a file where initialization data can be read.";

    private static final String CONJUGATE_DUMMY_VARIABLE = "z";

    
    private static final String MAIN_MENU = "[I]wasawa \t"
	    + "[R]e-order \t"
	    + "[C]onjugate\t"
	    + "s[U]bstitution\tc[O]nstrain\n" 
	    +"custom [V]ariables (No "+CONJUGATE_DUMMY_VARIABLE+"!)\t"
	    +"act by [W]eyl elt\t"
	    +"add/r[E]store Sunis\n"
//	    + "[N]ext Piece\t"
//	    + "Switch [P]iece\t"
	    + "psi [F]actor\t"
	    + "[S]ave \t" 
	    + "Save [A]s \t" 
	    + "[L]oad .ser\t" 
	    + "[D]rop last\t" 
	    /*+ "[K]ill\t"*/
	    + "[Q]uit\n" + "[L]ist [R]oots";

    
    
    private static final String SETUP_FILE_SYNTAX = "d\\\\g\\\\j\\\\p\\\\q\\\\r\\\\u\\\\w";

    static final Pattern QUIT_PATTERN = Pattern.compile("qu?i?t?",Pattern.CASE_INSENSITIVE);

    private static final Pattern BAIL_PATTERN = Pattern.compile("ba?i?l?",Pattern.CASE_INSENSITIVE);

    private static final Pattern REORDER_PATTERN = Pattern.compile("re?o?r?d?e?r?",Pattern.CASE_INSENSITIVE);

    private static final Pattern CONJUGATE_PATTERN = Pattern.compile("co?n?j?u?g?a?t?e?",Pattern.CASE_INSENSITIVE);

    private static final Pattern CONSTRAIN_PATTERN = Pattern.compile("c?on?s?t?r?a?i?n?",Pattern.CASE_INSENSITIVE);
    
    private static final Pattern LIST_ROOTS_PATTERN = Pattern.compile("li?s?t? ?ro*t?s?",Pattern.CASE_INSENSITIVE);
    
    private static final Pattern LIST_PSI_ROOTS_PATTERN = Pattern.compile("li?s?t\\s*ps?i?\\s*ro*t?s?",Pattern.CASE_INSENSITIVE);

    private static final Pattern SUBSTITUTION_PATTERN = Pattern.compile("s?ub?s?t?i?t?u?t?i?o?n?",Pattern.CASE_INSENSITIVE);

    private static final Pattern VARS_PATTERN = Pattern.compile("va?r?s?",Pattern.CASE_INSENSITIVE);

    private static final Pattern WEYL_PATTERN = Pattern.compile("we?y?l?",Pattern.CASE_INSENSITIVE);

    private static final Pattern ADD_PATTERN = Pattern.compile("(add)|(r?es?t?o?r?e?)",Pattern.CASE_INSENSITIVE);

    private static final Pattern PSI_PATTERN = Pattern.compile("p?s?i?\\s*fa?c?t?o?r?",Pattern.CASE_INSENSITIVE);

    private static final Pattern SAVE_PATTERN = Pattern.compile("sa?v?e?",Pattern.CASE_INSENSITIVE);

    private static final Pattern SAVE_AS_PATTERN = Pattern.compile("a",Pattern.CASE_INSENSITIVE);

    private static final Pattern LOAD_PATTERN = Pattern.compile("lo?a?d?",Pattern.CASE_INSENSITIVE);

    private static final Pattern DROP_PATTERN = Pattern.compile("dr?o?p?",Pattern.CASE_INSENSITIVE);

    private static final Pattern IWASAWA_PATTERN = Pattern.compile("iw?a?s?a?w?a?",Pattern.CASE_INSENSITIVE);
    private static final String PLAINTEXT_LOG_MESSAGE = "This file is a plaintext log file created and maintained \n" +
    		"by egut program SectionIntegralCalculator.";
    private static final String WHICH_BRANCH_MESSAGE = "Continue computation with study of\n" +
    		"\t(O) the integral over the integers\n" +
    		"\t(F) the integral over the nonintegers\n" +
    		"\t(N) neither.";
    private static final Pattern INTEGERS_PATTERN = Pattern.compile("O",Pattern.CASE_INSENSITIVE);
    private static final Pattern NONINTEGERS_PATTERN = Pattern.compile("F-?O?",Pattern.CASE_INSENSITIVE);
    private static final Pattern NEITHER_PATTERN = Pattern.compile("ne?i?t?h?e?r?",Pattern.CASE_INSENSITIVE);
    private static final OutputStyle SCREEN = OutputStyle.SCREEN;
    private static final String REORDER_ERROR_MESSAGE = "Enter an integer.  A negative integer " +
    		"to move on.";
     static final Pattern ARG_EXPRESSION_PATTERN = Pattern.compile("\\s*arg\\s*x\\s*_?\\s*\\{?\\s*\\-?\\s*(\\d\\s*,?\\s*)+\\}?\\s*" +
    		"(p\\s*\\d+)?\\s*(not\\s*)?in(.+\\+)?\\s*O");// set back to private TODO
    static final Pattern NONNEGATIVE_INTEGER_PATTERN = Pattern.compile("\\d*");
    private static final Pattern SUNI_POSITION_PATTERN = Pattern.compile("\\s*(arg)?\\s*x\\s*_?\\s*\\{?\\s*\\-?\\s*(\\d\\s*,?\\s*)+\\}?\\s*(p\\s*\\d+)?\\s*");


    public static final int SCREEN_WIDTH = edu.siu.math.egut.io.Constants.SCREEN_WIDTH;


    private static final Pattern NEXT_PATTERN = Pattern.compile("\\>");


    private static final Pattern PREVIOUS_PATTERN = Pattern.compile("\\<");


    private static final byte[] TEX_FOOTER_BYTE =Algorithms.TEX_FOOTER.getBytes();
 
 
    public SectionIntegralCalculator(String name, SectionIntegral integral, 
	    File directory, RandomAccessFile laTeXLog,
	    BufferedWriter plainTextLog) throws IOException {
	super();
	this.integral = integral;
	this.directory = directory;
	this.laTeXLog = laTeXLog;
	this.plainTextLog = plainTextLog;
	echoLog = new BufferedWriter(new FileWriter(ECHO_LOG_FILE));
	
    }

    /**
     * Instantiates a new SectionIntegralCalculator with a given name,
     * integral, and directory name, after performing various checks
     * on the names of the directory, tex log, and plaintext log.  
     * @param name1 name for the integral
     * @param si integral to be calculated
     * @param directoryName should name a directory
     * @throws IOException if trouble reading files or instantiating Writers
     */
    public SectionIntegralCalculator(final String name1, final SectionIntegral si,
	    final String directoryName) throws IOException {
	integral = si;
	name = name1;
	// is directoryName the name of a file?
	directory = getDir(directoryName);
	directory.mkdir();
	laTeXLog =getTeXLog(directoryName + File.separator + "log.tex");
	plainTextLog = getPlainTextLog(directoryName + File.separator + "log.txt");
	echoLog = new BufferedWriter(new FileWriter(ECHO_LOG_FILE));

	
    }

    public SectionIntegralCalculator(String name1, SectionIntegral si,
	    String directoryName, boolean b) throws IOException {
	
	
	integral = si;
	name = name1;
	// is directoryName the name of a file?
	directory = getDir(directoryName);
	directory.mkdir();
	if(b)
	    SectionIntegral.save(
		    directoryName + File.separator+name1+".ser", si);
	laTeXLog =getTeXLog(directoryName + File.separator + "log.tex");
	plainTextLog = getPlainTextLog(directoryName + File.separator + "log.txt");
	echoLog = new BufferedWriter(new FileWriter(ECHO_LOG_FILE));
	
    }

    /**
     * @param args ignored
     *   
     *  
     */
    public static void main(String[] args)   {
	//initialize scanner
	Scanner scanner = new Scanner(System.in);
	
	//initialize state log for this session
	ArrayList<String> stateLog = new ArrayList<String>();
	
	// all output is echoed to a log file, which means pretty much 
	// everything throws IOExceptions...
	try {
	    echoLog = new BufferedWriter(new FileWriter( ECHO_LOG_FILE));

	// get initialization
	    SectionIntegralCalculator sic = getInitialization();
	    if (sic == null) {
		say("Initialized to null.  Exiting");
		System.exit(0);
	    }
	    // add name to stateLog
	    stateLog.add(sic.name);

	    // write start message to laTeXLog
	    initializeLogs(sic);

	    // initialize boolean variable for being done
	    boolean done = false;
	    
	    

	    // while not done
	    while (!done) {
		// display state of calculation, plus menu options
		say(sic.name);
		say(sic.getIntegral().toString(OutputStyle.SCREEN));
		say(MAIN_MENU);

		// receive input,
		Input input = getInput(scanner);
		
		// perform requested operation
		sic.execute(input, stateLog, scanner);
		
		

	    }// end while not done
	    try {
		sic.laTeXLog.writeBytes(Algorithms.TEX_FOOTER);
		sic.laTeXLog.close();
		sic.plainTextLog.close();
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	} catch (IOException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}

   }

    private void execute(Input input, ArrayList<String> stateLog, Scanner scanner) throws IOException {
	// copy current name for comparison later
	String oldName = name;

	// Call requested method
	switch (input) {
	case PREVIOUS:
	    doPrevious(stateLog, scanner);
	    break;
	case NEXT:
	    say("no next state.  current state is last state.");
	    break;
	case LIST_PSI_ROOTS:
	    say("The following list contains the root of each sunipotent which is\n " +
	    		"associated to a string that appears in the psi factor.\nRoots of " +
	    		"sunipotents with arguments which contain variables that\n " +
	    		"appear in the psi, but are not associated to these variables\n" +
	    		"are not considered psi roots for these purposes.\n"+Algorithms.writeLieMatrix(integral.getPsiRoots()));
	    break;
	case LIST_ROOTS:
	    say(Algorithms.writeLieMatrix(integral.getU().getRoots()));
	    break;
	case QUIT:
	    quit(stateLog);
	    break;
	case CONSTRAIN:
	    try {
		doConstrain(scanner, stateLog);
	    } catch (IOException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    } catch (PolynomialReadException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    }
	    break;
	case IWASAWA:
	    try {
		doIwasawaBifurcation(scanner);

	    } catch (BadSubstitutionException e) {
		e.printStackTrace();
	    } catch (IOException e) {
		e.printStackTrace();
	    } catch (UncomputableJacobianException e) {
		e.printStackTrace();
	    } catch (NotARootException e) {
		e.printStackTrace();
	    }
	    break;

	case LOAD:
	    try {
		doLoad();
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	    break;

	case REORDER:
	    try {
		doReorder(scanner);
	    } catch (BadSubstitutionException e) {
		e.printStackTrace();
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	    break;

	case CONJUGATE:
	    try {
		doConjugate(scanner);
	    } catch (BadSubstitutionException e) {
		e.printStackTrace();
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	    break;
	case SAVE:
	    SectionIntegral.save(directory + File.separator
		    + name + ".ser", integral);
	    break;

	case SAVE_AS:
	    doSaveAs(scanner);
	    break;

	case SUBSTITUTION:

	    try {
		doSubstitution(scanner);
	    } catch (IOException e) {
		e.printStackTrace();
	    } catch (BadSubstitutionException e) {
		e.printStackTrace();
	    } catch (UncomputableJacobianException e) {

		e.printStackTrace();
	    }
	    break;

	case CUSTOMIZE_VARIABLES:
	    try {
		doCustomizeVariables(scanner);
	    } catch (BadSubstitutionException e) {
		e.printStackTrace();
	    } catch (IOException e) {
		e.printStackTrace();
	    } catch (UncomputableJacobianException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    }
	    break;
	case PSI_FACTOR:
	    try {
		doPsiFactor(scanner);
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	    break;

	case WEYL_ELEMENT:

	    try {
		doWeylElement(scanner);
	    } catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    } catch (WeylWordReadException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    } catch (BadSubstitutionException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    } catch (UncomputableJacobianException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    } catch (NotARootException e) {
		
		e.printStackTrace();
		say("Perhaps a variable which looked too much like a default variable and ended in a non-root?");
	    }
	    break;

	case ADD:
	    try {
		doAdd(scanner);
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	    break;

	case DROP_LAST:

	    try {
		doDropLast(scanner);
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	    break;
	}// end switch
	
	//deal with state log
	// if operation was previous or next, then leave it.
	// otherwise, if operation has changed name, then the new name should be added to the stateLog
	if(!(oldName.equals(name)||input==Input.PREVIOUS||input==Input.NEXT)){
	    /*If oldName does not appear at the end of the state log, add it.  
	     * This should occur if and only if the old state was reached by searching 
	     * through the state log.  When the user searches through the state log using 
	     * previous and next, the program should not store all the states passed
	     * through.  But when the user finds the state s/he wants and proceeds with 
	     * some other operation, the state log should store both the state that
	     * was selected and the state that resulted from the operation. 
	     */
	    if(!oldName.equals(stateLog.get(stateLog.size()-1)))
		stateLog.add(oldName);
	    stateLog.add(name);
	}

    }

    private void doSaveAs(Scanner scanner) throws IOException {
	    say("new name?");
	    ArrayList<String> notes = new ArrayList<String>();
	    notes.add(name);
	    name = scanner.nextLine();
	    notes.add(name);
	    notes.add(integral.toString(OutputStyle.TEX, name));
	    SectionIntegral.save(directory + File.separator
		    + name + ".ser", integral);
	    plainTextLog.write("save as "+name +"\n");
	    laTeXLog.writeBytes(latexLogEntry(Input.SAVE_AS, notes));
	
    }

    private void doNext(ArrayList<String> stateLog, Scanner scanner, int position) throws IOException {
	//check validity
	if( position <0 || position >=stateLog.size()){
	    say("attempt to access position "+position+ " in a list with "+stateLog.size()+" elements.");
	}else if( position == stateLog.size()-1 ){
	    say("no next state:  current state is last.");
	}else{
	    // adjust position 
	    position +=1;
	    // show user the integral at that position
		name = stateLog.get(position);
		integral = SectionIntegral.load(directory.getAbsolutePath()+File.separator+name+".ser");
		say(integral.toString());
	}    
		//get input
		Input input = getInput(scanner);
		switch (input) {
		case PREVIOUS:
		    doPrevious(stateLog, scanner, position);
		    break;
		case NEXT:
		    doNext(stateLog, scanner, position);
		    break;
		default:
		    execute(input, stateLog, scanner);
		}


	
	
    }

    private void doPrevious(ArrayList<String> stateLog, Scanner scanner) throws IOException {
	// initialize position
	int position = stateLog.size()-2;
	
	//show user the integral at that position
	name = stateLog.get(position);
	integral = SectionIntegral.load(directory.getAbsolutePath()+File.separator+name+".ser");
	say(integral.toString());
	
	//get input
	Input input = getInput(scanner);
	switch (input) {
	case PREVIOUS:
	    doPrevious(stateLog, scanner, position);
	    break;
	case NEXT:
	    doNext(stateLog, scanner, position);
	    break;
	default:
	    execute(input, stateLog, scanner);
	}
	
    }
    private void doPrevious(ArrayList<String> stateLog, Scanner scanner, int position) throws IOException {
	
	//check validity
	if( position <0 || position >stateLog.size()-1){
	    say("Attempt to access position "+ position+ " in a list with "+stateLog.size()+" elements.");
	}else if( position == 0)
	    say("No previous state.  Current state is first state.");
	else{
	
	// decrement position
	position --;
	
	//show user the integral at that position
	name = stateLog.get(position);
	integral = SectionIntegral.load(directory.getAbsolutePath()+File.separator+name+".ser");
	say(integral.toString());
	}
	
	//get input
	Input input = getInput(scanner);
	switch (input) {
	case PREVIOUS:
	    doPrevious(stateLog, scanner, position);
	    break;
	case NEXT:
	    doNext(stateLog, scanner, position);
	    break;
	default:
	    execute(input, stateLog, scanner);
	}
	
    }

    private void doConstrain(Scanner scanner, ArrayList<String> stateLog) throws IOException, PolynomialReadException {
	// declare array of strings for log notes
	ArrayList<String> notes = new ArrayList<String>();
	// add current name to notes
	notes.add(name);
	    say("input constraint");
	    boolean done = false;
	    while( !done ){
	    // get input
		String constraint = scanner.nextLine();
		Inequality newBound = parse(constraint, scanner);
		if( newBound != null){
		    
		    notes.add(newBound.toString(OutputStyle.TEX));
		    notes.add(newBound.toString(OutputStyle.SCREEN));
		    integral.getD().addBound(newBound);
		    integral.checkEnd();
		    name = updateName(Input.CONSTRAIN, name);
		    notes.add(name);
		    notes.add(integral.toString(OutputStyle.TEX, name));
		    saveAndLog(Input.CONSTRAIN, notes);
		    done = true;
		    
		}
		else if(QUIT_PATTERN.matcher(constraint).matches())
		    quit(stateLog);
		else if(BAIL_PATTERN.matcher(constraint).matches())
		    done = true;
		else{
		    say("Input was not understood.  Try again. (\"quit\" to quit, \"bail\" to bail.)");
		}
	    }// end while not done
	
    }
    private String updateName(Input input, String name2) {
	// get descriptive name
	String descriptiveName = descriptiveName(input, name2);

	// check whether the descriptive name is not available
	if( isAvailable(descriptiveName)){
		// if the descriptive name is available, return it
		return descriptiveName;
	    
	    
	}
	    // try distinguishing using a timestamp
	    String timeStampedName = descriptiveName+timeStamp();
	    if( isAvailable(timeStampedName))
		return timeStampedName;
	    
	    int counter = 1;
	    while(true){
		if( isAvailable(timeStampedName+"_"+counter))
		    return timeStampedName+"_"+counter;
		counter++;
	    }

    }

    private boolean isAvailable(String s) {
	
	return  !(new File(directory.getAbsolutePath() + File.pathSeparator + s + ".ser").exists());

    }

    private static  String timeStamp() {
	
	return "_"+(Calendar.getInstance().get(Calendar.MONTH)+1)
	+Calendar.getInstance().get(Calendar.DAY_OF_MONTH)
	+"_"
	+Calendar.getInstance().get(Calendar.HOUR_OF_DAY)
	+Calendar.getInstance().get(Calendar.MINUTE);
    }

    private void doConstrain(String constraint, Scanner scanner) throws IOException, PolynomialReadException {
	// declare array of strings for log notes
	ArrayList<String> notes = new ArrayList<String>();
	// add current name to notes
	notes.add(name);
		Inequality newBound = parse(constraint, scanner);
		if( newBound != null){
		    
		    notes.add(newBound.toString(OutputStyle.TEX));
		    notes.add(newBound.toString(OutputStyle.SCREEN));
		    integral.getD().addBound(newBound);
		    integral.checkEnd();
		    name = updateName(Input.CONSTRAIN, name);
		    notes.add(name);
		    notes.add(integral.toString(OutputStyle.TEX, name));
		    saveAndLog(Input.CONSTRAIN, notes);
		    
		}
		else if(QUIT_PATTERN.matcher(constraint).matches())
		    quit(stateLog);
		else{
		    say("\""+constraint +"\" was not understood.");
		}
	
    }
    private void doConstrain(Inequality newBound ) throws IOException {
	// declare array of strings for log notes
	ArrayList<String> notes = new ArrayList<String>();
	// add current name to notes
	notes.add(name);
		if( newBound != null){
		    
		    notes.add(newBound.toString(OutputStyle.TEX));
		    notes.add(newBound.toString(OutputStyle.SCREEN));
		    integral.getD().addBound(newBound);
		    integral.checkEnd();
		    name = updateName(Input.CONSTRAIN, name);
		    notes.add(name);
		    notes.add(integral.toString(OutputStyle.TEX, name));
		    saveAndLog(Input.CONSTRAIN, notes);
		    
		}
		else
		    say("New constraint is null.");
	
    }

    Inequality parse(String constraint, Scanner scanner) throws IOException, PolynomialReadException {
	// try to read as expression in args is in integers
	// TODO: unfortunately "indexOf" does not support regexps, 
	/*for this reason, it's possible to match ARG_EXPRESSION_PATTERN
	 * without actually being parsable.  
	 * 
	 * Priority one is to make the program work if the input is typed
	 * correctly.  But at some point one should make the program fix 
	 * any problems in the input or prompt for corrected input if unable to.
	 */
	if( ARG_EXPRESSION_PATTERN.matcher(constraint).matches()){
	    Sunipotent suni = getSuni(constraint, scanner);
	    if( suni == null)
		return null;
	    Polynomial p = edu.siu.math.egut.io.Algorithms.getPoly(
		    polySubstring(constraint), scanner);
	    if( constraint.indexOf("not in") > 0 && constraint.indexOf("not in") < constraint.indexOf("in") )
		return new Inequality(p.minus(suni.getArgument()), Symbol.GREATERTHAN, Polynomial.create(1));
	    else{
		
		return new Inequality(Polynomial.create(1), Symbol.GREATERTHANOREQUALTO, p.minus(suni.getArgument()));
		    
	    }
		
		    
	}else if( Inequality.INEQUALITY.matcher(constraint).matches()) {
	    try{
	    return Inequality.read(constraint);
	    }catch(InequalityReadException e){
		e.printStackTrace();
		return null;
	    } catch (PolynomialReadException e) {
		e.printStackTrace();
		return null;
	    }
	}
	return null;
    }

    private String polySubstring(String constraint) throws PolynomialReadException {
	if( constraint.indexOf("+O", constraint.indexOf("in")) >= 0)
	return constraint.substring(constraint.indexOf("in")+2,
		constraint.indexOf("+O", constraint.indexOf("in")+2)
			    			    ).trim();	
	if(
		constraint.indexOf("O", constraint.indexOf("in"))
	    <= 0)
	    throw new PolynomialReadException(constraint + "needs to end in \"in ... +O\" or \"in O\" " +
	    		"for polySubstring to do its job.");
	if(!constraint.substring(constraint.indexOf("in")+2,
		constraint.indexOf("O", constraint.indexOf("in")+2)
	    ).trim().matches(""))
	    throw new PolynomialReadException(constraint + "needs to end in \"in ... +O\" or \"in O\" " +
		"for polySubstring to do its job.");
	return "0";
	    
    }

    private boolean isSomethingPlusOrMinus(String x, Polynomial p) {
	return p.plus(x).isIndependentOf(x) || p.minus(x).isIndependentOf(x) ;
    }

    Sunipotent getSuni(String constraint, Scanner scanner) throws IOException { 
	
	
	String[] rootAndPosition = constraint.substring(
		constraint.indexOf("arg")+3, 
			((constraint.indexOf("not",constraint.indexOf("arg")+3)==-1)?
				(constraint.indexOf("in", constraint.indexOf("arg")+3)):
				    (constraint.indexOf("not",constraint.indexOf("arg")+3))
			))		
			.replaceAll("\\s", "").replaceAll("\\{","").replaceAll("\\}", "").replaceAll("_", "")
			.replaceAll("x", "").split("p");
	
	try {
	    ArrayList<Integer> options = new ArrayList<Integer>();
	    int[] root = edu.siu.math.egut.io.Algorithms
		    .rootFromString(rootAndPosition[0]);
	    int positionInt = -1;
	    if (rootAndPosition.length > 1) {
		positionInt = Integer.parseInt(rootAndPosition[1]);
		if (positionInt >= integral.getU().getNumberOfTerms())
		    say("Position " + positionInt + " does not exist.  "
			    + "\n(Position runs from 0 to "
			    + integral.getU().getNumberOfTerms() + ".)");
		else if (!Arrays.equals(integral.getU().get(positionInt)
			.getRoot(), root)) {
		    say("x_{" + rootAndPosition[0]
			    + "} does not appear at position "
			    + rootAndPosition[1] + ".");
		    options.add(positionInt);
		} else
		    return integral.getU().get(positionInt);

	    }// end if position was specified.
	    
	    /* 
	     * If position was not specified, it's likely that it didn't need to be.
	     */
	    else if( integral.getU().indexOf(root) == integral.getU().lastIndexOf(root))
		if( integral.getU().indexOf(root) != -1)
		    return integral.getU().get(integral.getU().indexOf(root));
		else
		    say("x_{" + rootAndPosition[0] +"} does not appear at all.");
	    
		for(int i = 0; i < integral.getU().getNumberOfTerms(); i++)
		    if( Arrays.equals(
			    integral.getU().get(i).getRoot(),
			    root))
			options.add(i);
	    
	    /*
	     * If we've reached this point there was a problem with the input.  
	     */
	    
	    display(options);
	    return getResponseSuni( scanner, options);
	    
	} catch (NumberFormatException e) {
	    return null;
	} catch (NotARootException e) {
	    return null;
	}	
	
	
	
    }

    private Sunipotent getResponseSuni(Scanner scanner, ArrayList<Integer> options) throws IOException {
	String response = scanner.nextLine().trim().replaceAll("\n","");
	if( NONNEGATIVE_INTEGER_PATTERN.matcher(response).matches()){
	    int responseInt = Integer.parseInt(response);
	    if( options.contains(responseInt))
		return integral.getU().get(responseInt);
	    else if( -1 < responseInt && responseInt < integral.getU().getNumberOfTerms())
		options.add(responseInt);
	}
	if( SUNI_POSITION_PATTERN.matcher(response).matches())
	    return getSuni(response, scanner);
	if( QUIT_PATTERN.matcher(response).matches()){
	    quit(stateLog);
	}
	if( BAIL_PATTERN.matcher(response).matches())
	    return null;
	    
	say("response was not understood.");
	display(options);
	return getResponseSuni(scanner, options);
    }
    private int getResponseInt(Scanner scanner, ArrayList<Integer> options) throws IOException {
	String response = scanner.nextLine().trim().replaceAll("\n","");
	if( NONNEGATIVE_INTEGER_PATTERN.matcher(response).matches()){
	    int responseInt = Integer.parseInt(response);
	    if( options.contains(responseInt))
		return responseInt;
	    else if( -1 < responseInt && responseInt < integral.getU().getNumberOfTerms())
		options.add(responseInt);
	}
	if( QUIT_PATTERN.matcher(response).matches()){
	    quit(stateLog);
	}
	if( BAIL_PATTERN.matcher(response).matches())
	    return -1;
	    
	say("response was not understood.");
	display(options);
	return getResponseInt(scanner, options);
    }

    private void quit(ArrayList<String> stateLog) throws IOException {
	writeStateLog( stateLog );
	laTeXLog.writeBytes(Algorithms.TEX_FOOTER);
	laTeXLog.close();
	plainTextLog.close();
	echoLog.close();
	System.exit(1);	
    }

    private void writeStateLog(ArrayList<String> stateLog) throws IOException {
	File stateLogFile = getUnusedStateLogFile();
	BufferedWriter sl = new BufferedWriter(new FileWriter(stateLogFile));
	for(String s:stateLog){
	    sl.write(s+"\n");
	    
	}
	sl.close();
	
	
    }

    private File getUnusedStateLogFile() {
	// start from default name
	String defaultName = "stateLog";
	File file = new File( directory.getAbsolutePath()
		+File.separator
		+ defaultName+".txt");
	if(!file.exists())
	    return file;
	
	// try time stamped name
	String timeStampedName= defaultName+timeStamp();
	file = new File( directory.getAbsolutePath()
		+File.separator
		+ timeStampedName+".txt");
	if(!file.exists())
	    return file;
	
	// just increment a counter to get something available
	int counter =1;
	while(true){
	    file = new File(directory.getAbsolutePath()
		    +File.separator
		    + timeStampedName+"_"+counter
		    +".txt");
	    if(!file.exists())
		return file;
	    counter++;
	}
    }

    private void display(ArrayList<Integer> options) throws IOException {
	for( int i: options)
	    say(i+":  "+integral.getU().get(i));
	
    }

    private void doDropLast(Scanner scanner) throws IOException {
	
	// declare array of strings for log notes
	ArrayList<String> notes = new ArrayList<String>();
	// add current name to notes
	notes.add(name);
	// warn user
	say("dropping last term inside integrand.\n(note that constraints in D are not" +
			" affected by this.)");
	// effect desired change on U
	integral.getU().remove(integral.getU().getLast());
	// get new integral name 
	say("what do you want to call the new integral?");
	name = getName(scanner);
	// add new name and TeX string representation  of integral to notes
	notes.add(name);
	notes.add(integral.toString(OutputStyle.TEX, name));
	
	
	// serialize and log 
	saveAndLog(Input.DROP_LAST, notes);
	
    }

    private void saveAndLog(Input input, ArrayList<String> notes) throws IOException {
	// serialize 
	SectionIntegral.save(defaultFileName(), integral);
	// latex Log
	laTeXLog.writeBytes(latexLogEntry(input, notes));
	// plaintext Log 
	plainTextLog.write(plainTextLogEntry(input, notes));
	
    }
    private void log(Input input, ArrayList<String> notes) throws IOException {
	// latex Log
	laTeXLog.writeBytes(latexLogEntry(input, notes));
	// plaintext Log 
	plainTextLog.write(plainTextLogEntry(input, notes));
	
    }

    private void doAdd(Scanner scanner) throws IOException {
	    
	// get desired data
	ArrayList<Sunipotent> newSunipotents = getNewSunis(integral.getG(),
		scanner);
	
	    // pass to adding method for integral
	    integral.addNewSunis(newSunipotents);
	    
	    // declare arrayList for log entry notes
		ArrayList<String> entryNotes = new ArrayList<String>();
		
		//add TEX string representation of added Sunipotents
		entryNotes.add(new Unipotent(integral.getG(),
			newSunipotents).toString(OutputStyle.TEX));
		
		//add present name of integral
		entryNotes.add(name);
	    
	    // prompt for new name
		say("what would you like to call this integral?");
		name = getName(scanner);
		
		// add new name to entry notes
		entryNotes.add(name);
		
		// add TeX string version of new integral to entry notes
		entryNotes.add(integral.toString(OutputStyle.TEX, name));
		
		// serialize
		SectionIntegral.save(directory.getCanonicalPath()+File.separator+name+".ser", integral);
		
		// latex log
		laTeXLog.writeBytes(latexLogEntry(Input.ADD, entryNotes ));

		// plaintext log
		this.plainTextLog.write(plainTextLogEntry(Input.ADD, entryNotes));

	    
    }

    private ArrayList<Sunipotent> getNewSunis(Group g, Scanner scanner)
	    throws IOException {
	boolean done = false;
	ArrayList<Sunipotent> answer = new ArrayList<Sunipotent>();
	while (!done) {
	    System.out.println("Root: (or 0 to bail)");

	    int[] newSuniRoot = Algorithms.getRoot(scanner, integral.getG());
	    if (newSuniRoot == null)
		done = true;
	    else {
		System.out.println("Variable:");
		/*
		 * TODO check robustness. add checking for problematic
		 * variables/ likely typos add "default option"
		 */
		String var = getAcceptableVariable(scanner);
		if (QUIT_PATTERN.matcher(var).matches() && !var.matches("q|Q")) {
		    say("Quit program? (String entered matches a request to quit\n"
			    + "and can not be used as a variable.)");
		    if (Algorithms.getYesOrNo(scanner)) {
			laTeXLog.writeBytes(Algorithms.TEX_FOOTER);
			laTeXLog.close(); 
			plainTextLog.close();
			System.exit(1);
		    } else {
			// tell em sorry ya gonna have to put in the root again.
			say("Ok.  I'm afraid you'll beed to re-enter the root.\n");
		    }
		}
		else if(BAIL_PATTERN.matcher(var).matches() &&
			!var.matches("b|B")){
		    done = true;
		}
		else{
		    answer.add(Sunipotent.create(newSuniRoot, var));
		}
	    }
	}
	return answer;
    }

    private void doWeylElement(Scanner scanner) throws IOException, WeylWordReadException, BadSubstitutionException, UncomputableJacobianException, NotARootException {
	WeylWord w1 = getWeylElement(integral.getG(), scanner);
	if( w1 != null ){
	    
	    //initialize notes list for logging
	    ArrayList<String> notes = new ArrayList<String>();
	    
	    // add String representation of word
	    notes.add(w1.toString(OutputStyle.TEX));
	    
	    // add current name
	    notes.add(name);
	    
	    //update name
	    name = updateName(Input.WEYL_ELEMENT, name);
	    
	    // add new name to log notes
	    notes.add(name);
	    
	    // modify integral
	    integral.actBy(w1);
	    
	    // add LaTeX representation of new integral to notes
	    notes.add(integral.toString(OutputStyle.TEX, name));
	    
	    // tex log 
	    laTeXLog.writeBytes(latexLogEntry(Input.WEYL_ELEMENT, notes));
	    
	    
	    // plaintext log
	    plainTextLog.write(plainTextLogEntry(Input.WEYL_ELEMENT, notes));
	    
	    // save integral
	    SectionIntegral.save(defaultFileName(), integral);
	    
	    
	    
	}
	
    }

    private String defaultFileName() {
	return directory.getAbsolutePath() + File.separator + name+".ser";
    }

    private String plainTextLogEntry(Input i, ArrayList<String> notes) {
	switch(i){
	case CONJUGATE:
	    return "Conjugate by " + notes.get(1) +".  Changes psi factor by "+notes.get(3)
	    +" add constraint "+ notes.get(4);
	case CONSTRAIN: 
	    return "Add constraint "+ notes.get(2)+" to  " + notes.get(0) + ". Get " + notes.get(3) +"\n"; 
	case PSI_FACTOR:
	    return "Change psi factor in  "+ notes.get(0)+ ".  Get "+ notes.get(1)+ "\n";
	case WEYL_ELEMENT:
	    return "Act on " + notes.get(1)+" by "+notes.get(0) +".  Get" + notes.get(2) +".\n";
	case ADD:
	    return "Add sunipotents to " + notes.get(1) + ". Get " + notes.get(2) +"\n"; 
	case DROP_LAST:
	    return "Drop last in " + notes.get(0)+ ".  Get "+ notes.get(1)+ "\n";
	}
	return "null entry\n";
    }

    private WeylWord getWeylElement(Group g, Scanner scanner) throws IOException {
	while(true){
	    String input = scanner.nextLine();
	    WeylWord w = WeylWord.parse(input, g);
	    if( w == null ){
		if(QUIT_PATTERN.matcher(input).matches())
		    quit(stateLog);//(closes log files before exiting. may throw IOException)
		if(BAIL_PATTERN.matcher(input).matches())
		    return null;
		say("Input could not be parsed.  Please try again.");
	    }
	    else 
		return w;
	}
    }

    private void doPsiFactor(Scanner scanner
	    ) throws IOException {
	say(integral.getQ().toMachineReadableString(OutputStyle.SCREEN));
	say("new factor:");
	getIntegral().setQ(
		edu.siu.math.egut.io.Algorithms.getPoly(scanner));
	say("what would you like to call this integral?");
	ArrayList<String> entryNotes = new ArrayList<String>();
	entryNotes.add(name);
	name = getName(scanner);
	entryNotes.add(name);
	entryNotes.add(integral.toString(OutputStyle.TEX, name));
	SectionIntegral.save(directory.getCanonicalPath()+File.separator+name+".ser", integral);
	
	laTeXLog.writeBytes(latexLogEntry(Input.PSI_FACTOR, entryNotes ));
	plainTextLog.write(plainTextLogEntry(Input.PSI_FACTOR, entryNotes));
    }

    private String latexLogEntry(Input i, ArrayList<String> notes) {
	switch(i){
	case SAVE_AS:
	    return "The integral \\eqref{"+ notes.get(0) + "} is saved as " + notes.get(1) + ".ser.  That is, " +
	    		"let "+notes.get(1) + " equal " + notes.get(2);
	case CONJUGATE:
	    return "Conjugate  by $x_{"+notes.get(1)+"}("+notes.get(2)+")$ in \\eqref{"+ notes.get(0)+"}.  \n" +
	    		"The $\\psi$-factor is changed by $"+notes.get(3) +".$  ";
	case CONSTRAIN:
	    return "Add constraint $" + notes.get(1) + "$ to \\eqref{"+ notes.get(0)+"}.  Get"  + notes.get(4);
	case IWASAWA:
	    return "By Iwasawa bifurcation, \\eqref{"+ notes.get(0) + "} equals\n"
	    +notes.get(2)+"\nplus\n"+notes.get(3);
	case PSI_FACTOR:
	    return "change of psi factor applied to \\eqref{" + notes.get(0) + "} yields" + notes.get(2);
	case WEYL_ELEMENT:
	    return "Act by Weyl element " + notes.get(0) + " on \\eqref{" + notes.get(1) + "} yields"+ notes.get(3);
	case ADD:
	    return "Add\n$$"+ notes.get(0) + "$$\n" +
	    		"at right in \\eqref{"+ notes.get(1)+"}.  Get"+ notes.get(3);
	case DROP_LAST:
	    return "Drop last in \\eqref{"+ notes.get(0)+"}.  Get"+ notes.get(2);
	default:
	    return "null entry.";
	}
	
    }

    private String getName(Scanner scanner) throws IOException {
	while(true){
	    String newName = scanner.nextLine();
	    if( isAcceptableFileName(newName)){
		if( new File(directory.getCanonicalPath()+File.separator+newName+".ser").exists()){
		    say("Serialized integral with that name exists.  Continue?");
		    
		    if( Algorithms.getYesOrNo(scanner) )
			return newName;
		   
		}
		else
		    return newName;
	    }
	    say("Please try again.");
	}

    }

    private boolean isAcceptableFileName(String newName) {
	if( newName.contains(File.separator)){
	    System.out.println("String containing \""+ File.separator + "\" not acceptable as a file name.");
	    return false;
	}
	if( newName.contains(File.pathSeparator)){
	    System.out.println("String containing \""+ File.pathSeparator + "\" not acceptable as a file name.");
	    return false;
	}
	if( newName.contains(".")){
	    System.out.println("String containing \".\" not acceptable as a file name.");
	    return false;
	}
	return true;
	
    }

    private void doCustomizeVariables(Scanner scanner) throws BadSubstitutionException, IOException, UncomputableJacobianException {
	boolean done = false;
	while(!done){
	    String variable = getVariable(integral, scanner);
	    if( variable == null)
		done = true;
	    else{
		String newVariable = getAcceptableVariable(scanner);
		integral.replaceWithSetArgs(variable, newVariable);
	    }
	}
	String name1 = updateName(Input.CUSTOMIZE_VARIABLES, name);
	laTeXLog.writeBytes("Changing names of some variables in \\eqref{" + name +"} yields" + integral.toString(OutputStyle.TEX, name1));
	plainTextLog.write("Customize vars in  "+ name + " to get " + name1+".\n");
	name = name1;
	SectionIntegral.save(defaultFileName(), integral);
	
    }

    private String getAcceptableVariable(Scanner scanner) throws IOException {
	while( true){
	    String input = scanner.nextLine();
	    if( isAcceptableAsAVariable(input))
		return input;
	    
	}
    }

    private boolean isAcceptableAsAVariable(String input) throws IOException {
	if (input.contains(SectionIntegral.PLACE_HOLDER_STRING)) {
	    say("A string containing \""+SectionIntegral.PLACE_HOLDER_STRING+"\" is not an acceptable substitute variable.");
	    return false;
	}
	if (input.contains("+")) {
	    say("A string containing \"+\" is not an acceptable substitute variable.");
	    return false;
	}
	if (input.contains("-")) {
	    say("A string containing \"-\" is not an acceptable substitute variable.");
	    return false;
	}
	if (input.contains("*")) {
	    say("A string containing \"*\" is not an acceptable substitute variable.");
	    return false;
	}
	if (input.contains("/")) {
	    say("A string containing \"/\" is not an acceptable substitute variable.");
	    return false;
	}
	if (input.contains("\\")) {
	    say("A string containing \"\\\" is not an acceptable substitute variable.");
	    return false;
	}
	if (input.contains("^")) {
	    say("A string containing \"^\" is not an acceptable substitute variable.");
	    return false;
	}
	if (input.contains(" ")) {
	    say("A string containing whitespace is not an acceptable substitute variable.");
	    return false;
	}
	if (input.contains("\n")) {
	    say("A string containing whitespace is not an acceptable substitute variable.");
	    return false;
	}
	if (input.contains("\t")) {
	    say("A string containing whitespace is not an acceptable substitute variable.");
	    return false;
	}
	return true;
    }

    private void doSubstitution(
	    Scanner scanner) throws IOException, BadSubstitutionException, UncomputableJacobianException {
	      //get variable 
	    System.out.println("Input a variable.");
	    String variable = getVariable(integral, scanner);
	    
	    
	    // get polynomial
	    System.out.println("Input the polynomial you wish to substitute for this variable.");
	    Polynomial q = edu.siu.math.egut.io.Algorithms.getPoly(scanner);
	    
	    integral = integral.replace(variable, q);
	    
	    String name1 = updateName(Input.SUBSTITUTION, name);
	    
	    laTeXLog.writeBytes(latexLogEntry(Input.SUBSTITUTION, q, variable, name, name1, integral));
	    plainTextLog.write( "Substitution: " + variable + "->" + q.toString() +" in " + name + ", get " + name1 +".\n");
	    name = name1;
	    SectionIntegral.save(defaultFileName(), integral);
	    
	
    }

    private String latexLogEntry(Input i, Polynomial q, String variable,
	    String name2, String name1, SectionIntegral integral2) {
	switch(i){
	case SUBSTITUTION:
	    return "Substituting $"+q.toString(OutputStyle.TEX) + "$ for $" + variable + "$ in \\eqref{" + name2 +"} yields" +
	    integral2.toString(OutputStyle.TEX, name1);
	default:
	    return "";
	}
    }

    
    /*FIXME:   because this is a static method, I can't use "quit()"
     * therefore if the user quits from here, the log files are not
     * closed properly.
     * 
     */
    private static String getVariable(SectionIntegral integral2, Scanner scanner) throws IOException {
	while(true){
	String input = scanner.nextLine();
	if(integral2.hasVariable(input))
	    return input;
	if(QUIT_PATTERN.matcher(input).matches())
	    System.exit(0);
	if(BAIL_PATTERN.matcher(input).matches())
	    return null;
	say("Input was not understood.  Please try again.  (\"quit\" to quit, \"bail\" to bail.)");
	}
	
    }

    private  void doConjugate(
	    Scanner scanner) throws BadSubstitutionException, IOException {
	
	
	//create ArrayList<String> for log notes
	ArrayList<String> notes = new ArrayList<String>();
	notes.add(name);
	
	    // Get a root. 
	    say("Input a root (as a "+ getIntegral().getU().getGroup().getRank()+" digit integer).\n");
	    int [] root = edu.siu.math.egut.io.Algorithms.getRoot(scanner, getIntegral().getU().getGroup());

	    // if the user bails on entering the root, then getRoot 
	    // returns null.  In this case we should just return to the menu.
	    if( root!= null){
	    notes.add(Algorithms.tupleToShortString(root));
	    //TODO this is a bit wasteful.  the root is input by user as String, converted to tuple, then converted back...
	    
	    SectionIntegral si1= getIntegral().times(
		    Sunipotent.create(root, Polynomial.create(CONJUGATE_DUMMY_VARIABLE)));
	    notes.add(CONJUGATE_DUMMY_VARIABLE);
	    /*TODO the blockage on a specific dummy variable is inconvenient and it can't be THAT hard to make it 
	     * check through the alphabet for an unused variable...
	     */
	    
	    
//	    say("initial:" + si1.getU().toString());
	    si1.lReduce();
//	    say("reduced: "+si1.getU().toString());
	    si1.makeChangesOfVariable();
//	    say("after C.O.V.: "+ si1.getU().toString());
//	    say("compare to: "+ sic.getIntegral().getU().toString());
	    
	    if( si1.getU().equals(getIntegral().getU())){
		// display difference in q to user
		say(si1.getQ().minus(getIntegral().getQ())+"\n");
		// add to notes
		notes.add(si1.getQ().minus(getIntegral().getQ()).toString(OutputStyle.TEX));
		if(suggestedConstraint(si1.getQ().minus(getIntegral().getQ()))!= null){
		    say("add constraint " + suggestedConstraint(si1.getQ().minus(getIntegral().getQ()))+"?");
		    if( getYesOrNo(scanner)){
			notes.add(suggestedConstraint(si1.getQ().minus(getIntegral().getQ())).toString(OutputStyle.TEX));
			// notes: name, root, variable, psi diff, constraint
			log(Input.CONJUGATE, notes);
			doConstrain(suggestedConstraint(si1.getQ().minus(getIntegral().getQ())));
		    }
			
		}
	    }
	    else {
		
		say(si1.getU()+"Conjugation failed.  This would occur, e.g., if \n" +
				"\t*the one-parameter subgroup corresponding to the root entered does not\n" +
				"\tnormalize the unipotent group under consideration." +
				"\n\t*the initial unipotent contained a Sunipotent with a root from the list\n" +
				"\tof invariant roots\n" +
				"\t*the root entered corresponds to the root of one of the elements of this " +
				"\n\tunipotent.");
		si1.getU().equalsVerbose(getIntegral().getU());
		
	    }
	    
	    } 
	
    }

 
	public static boolean getYesOrNo(final Scanner scanner) {
	    boolean done = false ;
	    while( ! done){
		String response = scanner.nextLine();
		if(response.matches("Y")|| response.matches("y")||response.matches("Yes")|| response.matches("yes"))
		    return true;
		if(response.matches("N")||response.matches("n")|| response.matches("no")|| response.matches("No"))
		    return false;
		System.out.println("Sorry.  I could not understand that.  Please input  \"Y\", \"N\", or \"quit.\"");
	    }
	    return false;
	}
    

    private static Inequality suggestedConstraint(Polynomial p) {
	if( p.isLinearWithConstantTermZeroIn( CONJUGATE_DUMMY_VARIABLE ) )
	    return new Inequality(Polynomial.create(1), 
		    Symbol.GREATERTHANOREQUALTO, 
		    p.coeff(CONJUGATE_DUMMY_VARIABLE));
	return null;
    }

    private void doReorder(Scanner scanner) throws BadSubstitutionException, IOException {
	// get reordering.
	boolean done = false;
	while(!done){
	    say(integral.getU().toStringWithIndices(true));
	    int input = getTerm(scanner);
	    if(input <0)
		done = true;
	    else if(input > integral.getU().getNumberOfTerms()-1)
		say(REORDER_ERROR_MESSAGE);
	    else{
		int toPosition = Algorithms.getInt(scanner);
		    if(toPosition <0)
			done = true;
		    else if(toPosition > integral.getU().getNumberOfTerms()-1)
			say(REORDER_ERROR_MESSAGE);
		    else{
			if( toPosition < input )
			    integral.move(input, toPosition);
			else
			    integral.move(input, toPosition+1);
			integral.cleanUp();
			integral.makeChangesOfVariable();
			integral.checkEnd();
			/*Will delete any sunipotents which appear at the far 
			 * right with integral arguments.  
			 * TODO check that this method works
			 * Also, a problem which having it be a SectionIntegral
			 * method is that it can't be logged.  Perhaps 
			 * that should be changed.
			 */
		    }
		
		
	    }
	}
	//update the name
	name = updateName(Input.REORDER, name);
	// save
	SectionIntegral.save(defaultFileName(), integral);
	// log
	laTeXLog.writeBytes("Writing the argument of $f_\\chi$ in terms " +
			"of simple unipotent elements ordered differently gives");
	laTeXLog.writeBytes(integral.toString(OutputStyle.TEX, name));
	
	plainTextLog.write("Reordering produces "+name+"\n");
	
	
    }

    private int getTerm(Scanner scanner) throws IOException {
	String input = scanner.nextLine();
	echoLog.write(input);
	if( BAIL_PATTERN.matcher(input).matches())
	    return -1;
	if( QUIT_PATTERN.matcher(input).matches())
	    quit(stateLog);
	int position = 0;
	int[] root = {0};
	
	try{
	position = Integer.parseInt(input);
	root = Algorithms.tupleFromString(input);
	}catch(NumberFormatException e){
	    say("Input was not understood.");
	    return getTerm(scanner);
	}
	if( -1 < position && position<integral.getU().getNumberOfTerms()){
	    if( integral.getU().indexOf(root) < 0 )
		return position;
	    else{
		ArrayList<Integer> options = new ArrayList<Integer>();
		options.add(position);
		addAllWithRoot(root,options);
		display(options);
		return getResponseInt(scanner, options);
		
	    }
	    
	}
	if( integral.getU().indexOf(root) < 0 ){
	    say("Response not understood.");
	    return getTerm(scanner);
	}
	if( integral.getU().indexOf(root) != integral.getU().lastIndexOf(root)){
		ArrayList<Integer> options = new ArrayList<Integer>();
		addAllWithRoot(root,options);
		display(options);
		return getResponseInt(scanner, options);
	    
	}
	return integral.getU().indexOf(root);
    
    }

    private void addAllWithRoot(int[] root, ArrayList<Integer> options) {
	for( int i = 0; i < integral.getU().getNumberOfTerms(); i++)
	    if(Arrays.equals(integral.getU().get(i).getRoot(), root))
		    options.add(i);
	
    }

    private String descriptiveName(Input input, String name2) {
	switch(input){
	case CONSTRAIN:
	    return updateName('C',name2);
	case CUSTOMIZE_VARIABLES:
	    return updateName('V',name2);
	case REORDER:
	    return updateName('R',name2);
	case SUBSTITUTION:
	    return updateName('S',name2);
	case WEYL_ELEMENT:
	    return updateName('W',name2);
	    
	    
	}
	return name2;
    }

    private String updateName(char c, String name2) {
	if(Pattern.compile(".*"+c+"\\d+").matcher(name2).matches())
	return name2.substring(0, name2.lastIndexOf(c)+1)
	+Integer.toString(
		1+Integer.parseInt(name2.substring(name2.lastIndexOf(c)+1)));
	else 
	return name2+c+"1";
    }

    private static void initializeLogs(SectionIntegralCalculator sic) throws IOException {
	try{
	    
	    // get the end of the file.
	    byte[] end = new byte[Algorithms.TEX_FOOTER.length()];
		sic.laTeXLog.seek(sic.laTeXLog.length()-Algorithms.TEX_FOOTER.length()-1);
		sic.laTeXLog.read(end,0,Algorithms.TEX_FOOTER.length());
	
		// if the file ends with \end{document} we overwrite that part.  if not, we assume there was
		// a crash which prevented a prevous session from writing end document, and write from the 
		// end of the file.
		if(Arrays.equals(end, TEX_FOOTER_BYTE))
		    sic.laTeXLog.seek(sic.laTeXLog.length()-Algorithms.TEX_FOOTER.length());
		else
		    sic.laTeXLog.seek(sic.laTeXLog.length());
	
	sic.laTeXLog.writeBytes("\n\n\n\\section{Computation of "
		+Algorithms.TIME_STAMP+"}\nStart from\n");
	
	sic.laTeXLog.writeBytes(sic.getIntegral().toString(OutputStyle.TEX, sic.name));
	
	//
	sic.plainTextLog.write("\n*************\nComputation of " + Algorithms.TIME_STAMP + "\n");
	sic.plainTextLog.write("Begin with integral " + sic.name+"\n");
	}catch(IOException e){
	    e.printStackTrace();
	    say("Unable to initialize logs. Exiting.");
	    System.exit(1);
	    
	}
    }

    private static SectionIntegralCalculator doLoad(SectionIntegralCalculator sic) throws IOException {
	
	SectionIntegralCalculator answer = getInitialization(sic.directory);	
	if( answer == null)
	    return sic;
	sic.laTeXLog.writeBytes(Algorithms.TEX_FOOTER);
	sic.laTeXLog.close();
	sic.plainTextLog.close();
	initializeLogs(answer);
	return answer;
    }
    private void doLoad() throws IOException {
	
	SectionIntegralCalculator answer = getInitialization(directory);	
	if( answer != null){
	    
	    name = answer.name;
	    integral = answer.integral;
	    this.plainTextLog.write("load " + name);
	    this.laTeXLog.writeBytes("load" + name + "first encountered in  \\eqref{"+name+"}" +
	    		" above and given by $$" + integral.toString(OutputStyle.TEX)+"$$");
	   
	}
    }

    private void doIwasawaBifurcation(Scanner scanner
	    ) throws BadSubstitutionException,
	    IOException, UncomputableJacobianException, NotARootException {
	
	if(!integral.getU().getLast().getArgument().isASignedMonomial()){
	    say("Argument of last Sunipotent is not a signed monomial.\n" +
	    		"This makes it impossible to do an Iwasawa bifurcation\n" +
	    		"at this time.  (You might try making some changes of \n" +
	    		"variable, or rearranging the Sunipotents.)");
	    
	}
	// create log notes
	ArrayList<String> notes = new ArrayList<String>();
	notes.add(name);
	notes.add(integral.toString(OutputStyle.TEX,name));
	
	
	// create integral for "integers" piece of iwasawa
	SectionIntegral iwasawa0 = integral.iwasawa0();
	// add string representation to notes
	notes.add(iwasawa0.toString(OutputStyle.TEX,name+"I0"));
	// serialize this integral
	SectionIntegral.save(directory.getCanonicalPath()
		+File.separator+name+"I0.ser", iwasawa0);
	
	
	// create integral for "nonintegers" piece of iwasawa
	SectionIntegral iwasawa1 =  integral.iwasawa1();
	// add string representation to notes
	notes.add(iwasawa1.toString(OutputStyle.TEX,name+"I1"));
	// serialize it 
	SectionIntegral.save(directory.getCanonicalPath()
		+File.separator+name+"I1.ser", iwasawa1);
	
	// record this step in the logs.
	plainTextLog.write("By Iwasawa bifurcation,"+ name +" = " + name+"I0 + " +
		name+"I1.\n");
	
	laTeXLog.writeBytes(
		latexLogEntry(Input.IWASAWA, notes));
	
	
	// then prompt the user for which becomes the active one.
	getIwasawaBranch(integral, iwasawa0, iwasawa1, scanner);

    }

   private void setIntegral(SectionIntegral si) {
	integral = si;
	
    }

private void getIwasawaBranch(
	    SectionIntegral si, SectionIntegral iwasawa0,
	    SectionIntegral iwasawa1, Scanner scanner) throws IOException {
	say(WHICH_BRANCH_MESSAGE);
	IwasawaBranchResponse ibr = getIwasawaBranchResponse(scanner);
	switch(ibr){
	case INTEGERS:
	    name = name +"I0";
	    break;
	case NONINTEGERS:
	    name = name +"I1";
	    break;
	    
	}
	    integral = SectionIntegral.load(defaultFileName());
	
	    
    }

/*FIXME:  if the user quits from here, the logs are not maintained
 * correctly.
 * 
 */
private static IwasawaBranchResponse getIwasawaBranchResponse(Scanner scanner) throws IOException {
    String s = scanner.nextLine();
    if(INTEGERS_PATTERN.matcher(s).matches())
	return IwasawaBranchResponse.INTEGERS;
    if(NONINTEGERS_PATTERN.matcher(s).matches())
	return IwasawaBranchResponse.NONINTEGERS;
    if(NEITHER_PATTERN.matcher(s).matches())
	return IwasawaBranchResponse.TRUNK;
    if(QUIT_PATTERN.matcher(s).matches())
	System.exit(1);
    say("Input was not understood.  Computation stays on trunk.");
    return IwasawaBranchResponse.TRUNK;
    
}


SectionIntegral getIntegral() {
	
	return integral;
    }

private static Input getInput(Scanner scanner) throws IOException {
	
	while(true){
	    String input = scanner.nextLine();
	    if (QUIT_PATTERN.matcher(input).matches())
		return Input.QUIT;
	    if (NEXT_PATTERN.matcher(input).matches())
		return Input.NEXT;
	    if (PREVIOUS_PATTERN.matcher(input).matches())
		return Input.PREVIOUS;
	    if( IWASAWA_PATTERN.matcher(input).matches())
		return Input.IWASAWA;
	    if( REORDER_PATTERN.matcher(input).matches())
		return Input.REORDER;
	    if( CONJUGATE_PATTERN.matcher(input).matches())
		return Input.CONJUGATE;
	    if( CONSTRAIN_PATTERN.matcher(input).matches())
		return Input.CONSTRAIN;
	    if( SUBSTITUTION_PATTERN.matcher(input).matches())
		return Input.SUBSTITUTION;
	    if( VARS_PATTERN.matcher(input).matches())
		return Input.CUSTOMIZE_VARIABLES;
	    if( WEYL_PATTERN.matcher(input).matches())
		return Input.WEYL_ELEMENT;
	    if( ADD_PATTERN.matcher(input).matches())
		return Input.ADD;
	    if( PSI_PATTERN.matcher(input).matches())
		return Input.PSI_FACTOR;
	    if( SAVE_PATTERN.matcher(input).matches())
		return Input.SAVE;
	    if( SAVE_AS_PATTERN.matcher(input).matches())
		return Input.SAVE_AS;
	    if( LOAD_PATTERN.matcher(input).matches())
		return Input.LOAD;
	    if( DROP_PATTERN.matcher(input).matches())
		return Input.DROP_LAST;
	    if( LIST_ROOTS_PATTERN.matcher(input).matches())
		return Input.LIST_ROOTS;
	    if( LIST_PSI_ROOTS_PATTERN.matcher(input).matches())
		return Input.LIST_PSI_ROOTS;
	    say("Input was not understood.  Please try again.");
	    
	}
	
    }
private static SectionIntegralCalculator getInitialization(File directory) throws IOException {
	SectionIntegralCalculator answer;
	File file = edu.siu.math.egut.io.Algorithms.getFile(directory);
	if( file == null)
	    return null;
	
	String fileName = file.getPath();
	if (fileName.endsWith(".ser")) {
	    say("file name ends with ser, indicating a serialized Object."); 

	    try {
		say("trying to load serialized object.");
		answer = SectionIntegralCalculator.load(file);
		return answer;

	    } catch (Exception e) {
		e.printStackTrace();
		try {
		    say("could not load ser object. trying to read input file.");
		    
		    answer = read(file);
		    return answer;
		} catch (Exception e1) {

		}

	    }
	}
	else{
	    say("file name does not end in ser.  should not be a serialized object.");
	    try {
		say("trying to read plaintext setup file.");
		say("read is happening from where you thought.");
		answer = read(file);
		say("read success.");
		return answer;

	    } catch (Exception e) {
		try {
		    e.printStackTrace();
		    say("failed to read setup file.");
		    System.exit(1);
		    answer = SectionIntegralCalculator.load(file);
		    return answer;
		} catch (Exception e1) {
		    say("failed to deserialize object.  fail.");
		    System.exit(1);

		}

	    }
	    
	}
	System.out.println("File could not be read as serialized SectionIntegral object\n" +
			"or as setup file with valid syntax.\n"+
			"Setup file syntax is:\n"+SETUP_FILE_SYNTAX);
	return null;
	    
	
	
}
static SectionIntegralCalculator getInitialization() throws IOException {
	SectionIntegralCalculator answer;
	File file = edu.siu.math.egut.io.Algorithms.getFile();
	if( file == null)
	    return null;
	
	String fileName = file.getPath();
	if (fileName.endsWith(".ser")) {
	    say("file name ends with ser, indicating a serialized Object."); 

	    try {
		say("trying to load serialized object.");
		answer = SectionIntegralCalculator.load(file);
		return answer;

	    } catch (Exception e) {
		e.printStackTrace();
		try {
		    say("could not load ser object. trying to read input file.");
		    
		    answer = read(file);
		    return answer;
		} catch (Exception e1) {

		}

	    }
	}
	else{
	    say("file name does not end in ser.  should not be a serialized object.");
	    try {
		say("trying to read plaintext setup file.");
		answer = read(file);
		return answer;

	    } catch (Exception e) {
		try {
		    e.printStackTrace();
		    say("failed to read setup file.");
		    System.exit(1);
		    answer = SectionIntegralCalculator.load(file);
		    return answer;
		} catch (Exception e1) {
		    say("failed to deserialize object.  fail.");
		    System.exit(1);

		}

	    }
	    
	}
	System.out.println("File could not be read as serialized SectionIntegral object\n" +
			"or as setup file with valid syntax.\n"+
			"Setup file syntax is:\n"+SETUP_FILE_SYNTAX);
	return null;
	    
	
	
}

    private static SectionIntegralCalculator load(File file) throws IOException  {
	SectionIntegral si;
	try {
	    si = SectionIntegral.load(file.getCanonicalPath());
	String directory1 = file.getParent();
	    return new SectionIntegralCalculator(dropSuffix(file.getName()), si, directory1);
	} catch (IOException e) {
	    
	    e.printStackTrace();
	}
	say(file.getPath() +" could not be loaded.  Exiting.");
	System.exit(1);// TODO maybe this is not what we'd want it to do
	return null;// if we were in the middle of a big calculation...
    }

//    private static BufferedWriter initializeNewPlainTextLog(File file) throws IOException {
//	say("Creating plain text log file.");
//	BufferedWriter answer  = new BufferedWriter(new FileWriter(file.getPath()+"/log.tex"));
//	
//	answer.write(PLAINTEXT_LOG_MESSAGE + Algorithms.TIME_STAMP);
//	return answer;
//    }
//
//    private static RandomAccessFile initializeNewLaTeXLog(File file) throws IOException {
//	    say("Creating LaTeX log file.");
//		FileWriter latexSetup = new FileWriter(file.getPath()+"/log.tex");
//		latexSetup.write(Algorithms.TEX_HEADER+Algorithms.TIME_STAMP+"\n"+Algorithms.TEX_FOOTER);
//		latexSetup.close();
//		
//		return new RandomAccessFile(new File (file.getPath()+"/log.tex"), "rw");
//	
//    }

    private static SectionIntegralCalculator read(File file)
	    throws DomainReadException,
	    InequalityReadException, ParabolicReadException,
	    UnipotentReadException, NotARootException, WeylWordReadException,
	    SectionIntegralReadException, IOException {
	
	
	SectionIntegral si = SectionIntegral.read(Algorithms
		.fileToString(file.getCanonicalPath()));
	String name1 = lastBit(dropSuffix(file.getName()));
	String directoryName = dropSuffix(file.getCanonicalPath()) + "files";
	
	try{
	return new SectionIntegralCalculator(name1, si, directoryName, true);
	}catch(Exception e){

	    e.printStackTrace();
	    System.exit(1);
	}
	say("something has gone wrong... exiting...");
	System.exit(1);
	return null;
    }

    private static String lastBit(String fileName) {
	
	return (fileName.lastIndexOf(File.separatorChar)==-1)?fileName:fileName.substring(fileName.lastIndexOf(File.separatorChar));
    }

    static String dropSuffix(String fileName) {
	
	return (fileName.lastIndexOf('.')== -1)?fileName:fileName.substring(0, fileName.lastIndexOf('.'));
    }

    
    
    private static void say(String string) throws IOException {
	echoLog.write(string);
	System.out.println(string);
	
    }

    public static boolean isTeXLogFormat(File texFile) {
	if(texFile.isDirectory())
	    return false;
	if(!texFile.canRead())
	    return false;
	String s;
	try {
	    s = Algorithms.fileToString(texFile.getCanonicalPath());

	} catch (FileNotFoundException e) {
	    return false;
	} catch (IOException e) {
	    return false;
	}
	    
	return s.startsWith("\\documentclass");
    }

    public static boolean isTxtLogFormat(File txtFile)  {
	if (txtFile.isDirectory())
	    return false;
	if (!txtFile.canRead())
	    return false;
	String s;
	try {
	    s = Algorithms.fileToString(txtFile.getCanonicalPath());
	} catch (FileNotFoundException e) {
	    return false;
	} catch (IOException e) {
	    return false;
	}
	return s.startsWith(Algorithms.removeWhiteSpace(PLAINTEXT_LOG_MESSAGE));
	
    }

    /**
     * Checks that a given filename specifies a 
     * directory, or does not exist.  Forces system 
     * exit if these criteria are not met.
     * @param dirName string name
     * @return directory.
     * @throws IOException 
     */
    public static File getDir(final String dirName) throws IOException {
	File answer = new File( dirName);
	if( answer.exists() && (!answer.isDirectory())){
	    say("File "+dirName+"exists, and is not a directory.  Exiting.");
	    System.exit(1);
	}
	return answer;
	
    }

    /**
     * Checks that the given filename specifies either a nonexistent file,
     * or an existing TeX log file.  Creates a new TeX log file 
     * in the first case, and returns the result.
     * @param texFileName 
     * @return 
     * @throws IOException 
     */
    public static RandomAccessFile getTeXLog(final String texFileName) throws IOException {
	File texFile = new File(texFileName);
	if (texFile.exists()) {
	    if (!isTeXLogFormat(texFile)) {
		say("File "
			+ texFileName
			+ " exists and is not an existing TeX Log file.  Exiting.");
		System.exit(1);
		
	    }
	}
	else{
	    FileWriter fw = new FileWriter(texFile);
	    fw.write(Algorithms.TEX_HEADER + Algorithms.TIME_STAMP +"\n\n\n" + Algorithms.TEX_FOOTER);
	    fw.close();
	
	}
	return new RandomAccessFile( texFile, "rw");

    }

    /**
     * @param txtFileName
     * @return
     * @throws IOException
     */
    public static BufferedWriter getPlainTextLog(String txtFileName) throws IOException {
	File file = new File(txtFileName);
	if( file.exists()){
	    if(!isTxtLogFormat(file)){
		say("File "+ txtFileName 
			+ "exists and is not an existing text log file." 
			+ txtLogFormatFailureDetails(file) + "Exiting");
		System.exit(1);
	    }
	}
	else{
	    FileWriter fw = new FileWriter( file );
	    fw.write(PLAINTEXT_LOG_MESSAGE);
	    fw.close();
	}
	return new BufferedWriter(new FileWriter(file, true));
    }

    private static String txtLogFormatFailureDetails(File txtFile) {
	if (txtFile.isDirectory())
	    return "is Directory";
	if (!txtFile.canRead())
	    return "not readable";
	String s;
	try {
	    s = Algorithms.fileToString(txtFile.getCanonicalPath());
	} catch (FileNotFoundException e) {
	    return "FileNotFoundException thrown.";
	} catch (IOException e) {
	    return "IOException thrown.";
	}
	if(! s.startsWith(Algorithms.removeWhiteSpace(PLAINTEXT_LOG_MESSAGE)))
	    return Algorithms.removeWhiteSpace(PLAINTEXT_LOG_MESSAGE) +" fails to match \n"+s;
	return "Unexplained failure.";
    }

    private void kill(Scanner scanner){
	// decide which root to attack
	// decide which root to try
	// try it
	// decide whether it does anything 
	// implement changes.  
	
    }
}
